/*
 * JOGRE (Java Online Gaming Real-time Engine) - Spades
 * Copyright (C) 2004  Bob Marks (marksie531@yahoo.com)
 * http://jogre.sourceforge.org
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package org.jogre.mayi.client;

import info.clearthought.layout.TableLayout;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.JTableHeader;

import nanoxml.XMLElement;

import org.jogre.client.TableConnectionThread;
import org.jogre.client.awt.JogreButton;
import org.jogre.client.awt.JogrePanel;
import org.jogre.client.awt.JogreTableFrame;
import org.jogre.common.IGameOver;
import org.jogre.common.Player;
import org.jogre.common.PlayerList;
import org.jogre.common.Table;
import org.jogre.common.comm.Comm;
import org.jogre.common.comm.CommGameOver;
import org.jogre.common.comm.CommNextPlayer;
import org.jogre.common.comm.CommStartGame;
import org.jogre.common.games.Card;
import org.jogre.common.util.JogreLabels;
import org.jogre.common.util.JogreLogger;
import org.jogre.mayi.common.CommMayIDiscard;
import org.jogre.mayi.common.CommMayIDrawCard;
import org.jogre.mayi.common.CommMayIHandScore;
import org.jogre.mayi.common.CommMayIMayI;
import org.jogre.mayi.common.CommMayIPlayCards;
import org.jogre.mayi.common.CommMayIScorecard;
import org.jogre.mayi.common.MayIDeal;
import org.jogre.mayi.server.MayIConstants;

/**
 * Spades table frame.
 *
 * @author Garrett Lehman (Gman)
 * @version Alpha 0.2.3
 */
public class MayITableFrame extends JogreTableFrame {

	// Declare the game data.
	private MayIModel model = null;

	// Declare components
	private MayIPlayerHandComponent[] playerHandComponents = null;
	private MayIPlayerComponent[] playerComponents = null;
	
	private MayIPlayAreaComponent [][] playAreaComponents = null;
	private MayIDeckComponent deckComponent = null;
	
	private MayIScoreboard scoreboard = null;
	private JTable scoreboardTable = null;
	private JScrollPane scoreboardScrollPane = null;
	
	private JPanel [] playAreaPanels = null;
	
	private JButton resetButton = null;
	private JButton mayIButton = null;

	// Declare the game controller
	private MayIController controller = null;

	// Logger for debugging
	private JogreLogger logger = new JogreLogger(this.getClass());

	/**
	 * Contructor for the spades table frame
	 *
	 * @param conn
	 *            Connection
	 */
	public MayITableFrame(TableConnectionThread conn) {
		super(conn);

		// Create game model and components
		this.model = new MayIModel(table.getNumOfPlayers());
		this.playerHandComponents = new MayIPlayerHandComponent[table.getNumOfPlayers()];
		this.playerComponents = new MayIPlayerComponent[table.getNumOfPlayers()];
		
		for (int i = 0; i < table.getNumOfPlayers(); i++) {
			this.playerComponents[i] = new MayIPlayerComponent();
			
			this.playerHandComponents[i] = new MayIPlayerHandComponent((i==0));
		}
		this.deckComponent = new MayIDeckComponent();		
		
		this.playAreaPanels = new JPanel[table.getNumOfPlayers()];
		for (int i = 0; i < table.getNumOfPlayers(); i++) {
			this.playAreaPanels[i] = new JPanel();
		}
		
		this.playAreaComponents = new MayIPlayAreaComponent[table.getNumOfPlayers()][MayIConstants.PLAY_AREAS];
		
		for (int i = 0; i < table.getNumOfPlayers(); i++) {
			for (int j = 0; j < MayIConstants.PLAY_AREAS; j++) {
				this.playAreaComponents[i][j] = new MayIPlayAreaComponent();
				this.model.addObserver(playAreaComponents[i][j]);
				
				this.playAreaPanels[i].add(this.playAreaComponents[i][j]);
			}
		}
		
		

		
		scoreboard = new MayIScoreboard();
		scoreboardTable = new JTable(scoreboard);
		scoreboardScrollPane = new JScrollPane(scoreboardTable);
		scoreboardTable.setPreferredScrollableViewportSize(new Dimension(150,
				125));
		scoreboardTable.setCellSelectionEnabled(false);
		JTableHeader header = scoreboardTable.getTableHeader();
		header.setReorderingAllowed(false);
		header.setResizingAllowed(false);

		// Add component observers to the game data.
				
		for (int i = 0; i < table.getNumOfPlayers(); i++) {
			this.model.addObserver(playerComponents[i]);
			this.model.addObserver(playerComponents[i]);
			//
		}
		this.model.addObserver(deckComponent);

		// Set up game controller
		this.controller = new MayIController(model, playerHandComponents,
				deckComponent, playAreaComponents);
		this.controller.setConnection(conn);
		this.playerHandComponents[0].setController(controller);
		this.deckComponent.setController(controller);
		
		for (int i = 0; i < table.getNumOfPlayers(); i++) {
			for (int j = 0; j < MayIConstants.PLAY_AREAS; j++) {
				this.playAreaComponents[i][j].setController(controller);
			}
		}
		
		

		// Set game data and controller (constructor must always call these)
		setupMVC(model, playerHandComponents[0], controller);

		
		this.resetButton = new JogreButton("Unplay Cards");
		resetButton.addActionListener ( new ActionListener () {
			public void actionPerformed (ActionEvent event) {
				controller.unplayCards();
			}
		});
		
		this.mayIButton = new JogreButton("MAY I");
		mayIButton.addActionListener ( new ActionListener () {
			public void actionPerformed (ActionEvent event) {
				System.out.println("mayi1");
				controller.callMayI();
			}
		});
		
		
		
		double pref = TableLayout.PREFERRED;
		double [][] topPanelSizes = new double [][] {{5, pref, 5, pref, 5}, {5, pref, 5}};
		JogrePanel secondPanel = new JogrePanel (topPanelSizes);
		secondPanel.setBorder(BorderFactory.createEtchedBorder());
		secondPanel.add (this.resetButton, "1,1");
		secondPanel.add (this.mayIButton, "3,1");
		
		TableLayout tl = (TableLayout) this.getTopPanel().getLayout();
		tl.setColumn(new double[]{pref, 5, pref, 5, pref});
		
		this.addToTopPanel(secondPanel, "4,0");
				
		// Create game panel and add components to it.		
		double [][] size = new double [2][];
		size [0] = new double[]{ 5, pref, 5, pref, 5, pref, 5, pref, 5 };
		size [1] = new double[table.getNumOfPlayers()*2+1];
		for (int i = 0; i < table.getNumOfPlayers(); i++) {
			size[1][i*2] = 5;
			size[1][i*2+1] = pref;
		}
		size[1][table.getNumOfPlayers()*2] = 5;
		
		JogrePanel gamePanel = new JogrePanel (size);
		gamePanel.add(deckComponent, "7,"+(table.getNumOfPlayers()*2-1)+",c,c");
		gamePanel.add(scoreboardScrollPane, "7,1,c,c");
		
		for (int i = 0; i < table.getNumOfPlayers(); i++) {
			int row = (table.getNumOfPlayers()*2-1)-(i*2);
			gamePanel.add(playerComponents[i], "1,"+row+",c,c");
			gamePanel.add(playerHandComponents[i], "3,"+row+",c,c");
			gamePanel.add(playAreaPanels[i], "5,"+row+",c,c");
		}
		
		

		// Add the game panel
		setGamePanel(gamePanel);

		// Pack the panel
		pack();		
	}


	/**
	 * Recieve the table message.
	 *
	 * @see org.jogre.client.ITable#receiveTableMessage(nanoxml.XMLElement)
	 */
	public void receiveMessage(XMLElement message) {
		String messageType = message.getName();

		logger.info("receiveMessage", "(" + this.conn.getUsername() + ") receive: " + messageType);
		
		if (messageType.equals(Comm.JOIN_TABLE)) {
			updatePlayerComponents(null);
		} else if (messageType.equals(Comm.PLAYER_STATE)) {
			updatePlayerComponents(null);
		} else if (messageType.equals(Comm.START_GAME)) {
			this.model.setSeatIndexes(this.controller.getSeatNum());
			CommStartGame startGame = new CommStartGame(message);
			String username = startGame.getUsername();
			updatePlayerComponents(username);
			this.scoreboard.initTable(this.table.getPlayerList());
		} else if (messageType.equals(Comm.NEXT_PLAYER)) {
			CommNextPlayer nextPlayer = new CommNextPlayer(message);
			String username = nextPlayer.getUsername();
			updatePlayerComponents(username);
			
			if (this.controller.isThisPlayersTurn()) {
				model.setGameState(MayIModel.GAME_STATE_PRE_TURN);
				//getMessageComponent().systemMessage("It's your turn");
			}
			
			//updateBiddingComponent();
		} else if (messageType.equals(MayIDeal.XML_NAME)) {
			
			
			MayIDeal deal = new MayIDeal(message);
			
			this.deckComponent.setDeck(deal.getDeck());
			for (int i = 0; i < this.playerHandComponents.length; i++) {
				this.playerHandComponents[i].initializeCards(deal.getHands().get(i));
			}
			model.setGameState(MayIModel.GAME_STATE_WAITING_TO_PLAY);
			
			int [] details = MayIConstants.ROUND_STRUCTURE[this.model.getRound()];
			
			for (int i = 0; i < table.getNumOfPlayers(); i++) {
				int j = 0;
				for (; j < details[1]; j++) {
					this.playAreaComponents[i][j].setType(MayIPlayAreaComponent.TYPE_RUN);
				}
				for (; j < details[2]+details[1]; j++) {
					this.playAreaComponents[i][j].setType(MayIPlayAreaComponent.TYPE_GROUP);
				}
			}

		} else if (messageType.equals(CommMayIDrawCard.XML_NAME)) {
			CommMayIDrawCard draw = new CommMayIDrawCard(message);
			oppDrawCard(draw);
		} else if (messageType.equals(CommMayIDiscard.XML_NAME)) {
			CommMayIDiscard discard = new CommMayIDiscard(message);
			oppDiscard(discard);
		} else if (messageType.equals(CommMayIScorecard.XML_NAME)) {
			CommMayIScorecard scorecard = new CommMayIScorecard(message);
			this.scoreboard.addScores(scorecard);
			this.scoreboardTable.repaint();
			this.displayRoundScores(scorecard);

			//this.controller.setRoundScoreAndBags(scores, bags);
			CommGameOver gameOver = scorecard.getGameOver();
			
			if (gameOver == null) {
				this.model.resetRound();
				this.nextRound();
				this.controller.requestDeal();
				//getMessageComponent().systemMessage("The Next Round has started");
				
			}
			else {
				gameOver(gameOver);
			}
		} else if (messageType.equals(CommMayIMayI.XML_NAME)) {
			CommMayIMayI mayi = new CommMayIMayI(message);
			this.controller.receiveMayI(mayi);
			getMessageComponent().systemMessage(mayi.getUsername() + " has called May I");
		} 
	}
	
	public void oppDiscard(CommMayIDiscard discard) {
		int seatNum = controller.getSeatNum(discard.getUsername());
		int seatIndex = this.model.getSeatIndex(seatNum);
		
		for (CommMayIPlayCards play : discard.getPlayedCards()) {
			int playSeatNum = controller.getSeatNum(play.getPlayer());
			int playSeatIndex = this.model.getSeatIndex(playSeatNum);
			MayIPlayAreaComponent comp = this.playAreaComponents[playSeatIndex][play.getIndex()];
			for (Card c : play.getCards()) {
				this.playerHandComponents[seatIndex].discard(c);
			}
			boolean valid = comp.playCards(play.getCards());
			comp.getNewCardsAdded();
		}
		
		if (discard.getCard() != null) {
			this.playerHandComponents[seatIndex].discard(discard.getCard());
			this.deckComponent.addDiscard(discard.getCard());
		}
		
		if (discard.getOut()) {
			CommMayIHandScore score = new CommMayIHandScore(
					conn.getUsername(), 
					this.playerHandComponents[0].getPoints());
			conn.send(score);
		}
	}
	
	public void oppDrawCard(CommMayIDrawCard draw) {
		int seatNum = controller.getSeatNum(draw.getDrawUser());
		int seatIndex = this.model.getSeatIndex(seatNum);
		Card c = null;
		if (draw.getType() == CommMayIDrawCard.FROM_DECK) {
			c = this.deckComponent.drawFromDeck();
			getMessageComponent().systemMessage(draw.getDrawUser() + " has drawn from the deck");
		}
		else {
			c = this.deckComponent.drawDiscard();
			getMessageComponent().systemMessage(draw.getDrawUser() + " has drawn the discard");
		}
		c.setFaceUp(false);
		
		this.playerHandComponents[seatIndex].addCard(c);
	}
	

	/**
	 * Update player components. If username is provided, then set the turn to
	 * that user.
	 *
	 * @param username
	 *            Username
	 */
	public void updatePlayerComponents(String username) {
		int index = 0;
		PlayerList players = table.getPlayerList();
		for (int i = 0; i < players.size(); i++) {
			Player player = players.getPlayer(i);
			index = i;
			if (this.model.seatIndexesSet())
				index = this.model.getSeatIndex(i);
			this.playerComponents[index].setPlayer(player);
			if (username != null) {
				if (player.getPlayerName().equals(username))
					this.playerComponents[index].setTurn(true);
				else
					this.playerComponents[index].setTurn(false);
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.jogre.client.awt.JogreTableFrame#startGame()
	 */
	public void startGame() {
		if (this.controller.isThisPlayersTurn())
			this.updatePlayerComponents(this.controller.getCurrentPlayer());
	}

	/**
	 * Changed functionality to the game over method of the JogreTableFrame.
	 * This method changes the message that displays at the end of a game by
	 * using "team" terms instead of "player" terms.
	 *
	 * @see org.jogre.client.awt.JogreTableFrame#gameOver(org.jogre.common.comm.CommGameOver)
	 */
	public void gameOver (CommGameOver commGameOver) {

		// Create game over message
		JogreLabels labels = JogreLabels.getInstance();
		String message = null;

		

		// Depending on the status create the game over message
		int status = commGameOver.getStatus();
		if (status == IGameOver.DRAW)
			message = labels.get ("game.ends.in.a.draw") + ".";
		else if (status == IGameOver.LOSE)
			message = "You " + labels.get ("loses.this.game") + ".";
		else if (status == IGameOver.WIN)
			message = "You " + labels.get ("wins.the.game") + ".";

		Table table = conn.getTableList().getTable (tableNum);
		table.gameOver();

		JOptionPane.showMessageDialog(this, message);

		updatePlayerComponents(null);
	}
	
	public void nextRound() {
		for (MayIPlayerHandComponent comp : this.playerHandComponents) {
			comp.reset();
		}
		
		for (int i = 0; i < table.getNumOfPlayers(); i++) {
			for (int j = 0; j < MayIConstants.PLAY_AREAS; j++) {
				this.playAreaComponents[i][j].reset();
			}
		}
	}
	
	public void displayRoundScores(CommMayIScorecard scorecard) {
		
		String message = "";
		String name = null;
		for (CommMayIHandScore score : scorecard.getScores()) {
			if (score.getScore() == 0) {
				name = score.getUsername();
			}
			message += "\n" + score.getUsername() + ": " + score.getScore();
		}
		message = name + " went out. Here are the hand scores:" + message;
		
		JOptionPane.showMessageDialog(this, message);
	}
}